Introductie

Gedurende de stage werkte ik voornamelijk aan de totstandkoming van een dataset binnen het project GLOBALISE bevattende plaatsen die personeel van de Vereenigde Oostindische Compagnie (VOC) noemde in zijn correspondentie. De dataset is bedoeld voor het annotatieproces van de transcripties van de correspondentie van de VOC. Daarnaast wil GLOBALISE de data publiceren, zodat andere onderzoekers er gebruik van kunnen maken. Mijn werkzaamheden brachten een aantal stage producten voort die ik hier opeenvolgend en tevens chronologisch behandel. De onderstaande afbeelding weergeeft de verschillende taken die leidden tot de uiteindelijke dataset.

Figuur 1. Proces en product Figuur 1. Proces en product

De details van de werkprocessen die ik tijdens mijn stage bij het Huygens Instituut uitvoerde staan hieronder uitgelegd. Voor nu is het voldoende om te weten dat ik niet de enige ben die dit alles heeft gedaan. Product A en D zijn niet van mijn hand. Product A is een susbet van de dataset die Brecht Nijman heeft gemaakt en vormde de basis waarop ik voortborduurde. Deze dataset bevatte de vermeldingen van plaatsnamen zoals opgenomen in de publicaties van de Generale Missiven (GM) (Coolhaas et al. 1960) van Gouverneurs-Generaal en Raden aan de Heren XVII van de Verenigde Oostindische Compagnie, het directoraat. De dataset bevat ook de indexen van geografische namen in het Corpus Diplomaticum Neerlando-Indicum (CD) (Sholihah, Roling, and Gerbrandy 2019) waarin de contracten die de VOC met lokale heersers sloot zijn opgenomen. De dataset ziet er zo uit:

De dataset bevat informatie over de standaardnaam zoals in de GM aangetroffen (standard_name_rgp_gm), een unieke plaatsnaam (name) vergezeld door een ID, een alternatieve naam in de CD en waar de desbetreffende plaats te vinden is in de GM of CD.

Product D is door Leon van Wissen aangeleverd. Het is de ruwe data die ik gebruikte voor de webapplicatie, een van de stageproducten. De dataset bevat alle woorden die meer dan één maal voorkomen in een inventarisnummer van de Overgekomen Brieven en Papieren (OBP). De OBP zijn de documenten die VOC-personeel verzond naar de Nederlanden. De dataset bevatte de frequenties per woord per jaar, inclusief het originele inventarisnummer.

Hieronder beschrijf ik het werk dat ik heb gedaan. Telkens is een geschatte tijdsinvestering vermeld, waarna een beschrijving volgt, eindigende met het uiteindelijke product. Dit product is terug te vinden in de portfolio onder dezelfde naam. Ik neem alleen de belangrijke (eind-)producten op in de portfolio.

1. Semi-automatische opschoning

Geschatte tijdsinvestering: 15%

Ter indicatie is de uiteindelijke structuur van de dataset de volgende:

Kolom Beschrijving
standard_name De standaardnaam is de vermelding in standard_rgp_gm
unique_name Elke unieke plaatsnaam, inclusief de vermeldingen in standard_name en plaatsnamen uit Exploring Slave Trade in Asia (ESTA) en de Atlas of Mutual Heritage (AMH)
source De bron waarin een bepaalde unieke plaatsnaam (in unique_name) voorkomt
x_id Het ID van een standaardplaatsnaam (in standard_name) binnen een bepaalde dataset x (GM/CD, AMH, ESTA, GeoNames)
latitude De breedtegraad van een plaats (WGS 84)
longitude De lengtegraad van een plaats (WGS 84)
coord_source De bron van het coördinaat (AMH, ESTA of GeoNames)
row_id Een uniek rij-ID voor elke rij in de vorm TEMP_x, indien aanwezig in de basisdataset, ADD_x als een plaatsnaam vervolgens door mij is toegevoegd
coord_certainty Coördinaatzekerheid naar de volgende categorieën: automatic, certain, uncertain en approximate
country_geofeature Huidige land waarin de plaats ligt
state_province_region De staat, provincie of regio van het land in country_geofeature

De variabelen country_geofeature en state_province_region zijn door Manjusha Kuruppath aangemaakt.

Een van de eerste werkzaamheden was het opschonen van de dataset die ik van Brecht kreeg. Eerst besloot ik om enkel een subset van de dataset te gebruiken uit praktische overwegingen. Hij telt namelijk 1,555 rijen, waardoor een keer werken met zulke tekstuele data niet behapbaar is. Daarom selecteerde ik alleen de rijen met plaatsnamen die een standaardplaatsnaam hebben in de GM, dus alle rijen met een observatie in standard_name_rgp_gm. De uiteindelijke dataset heeft namelijk ook een variabele standard_name, dus dit was een goed startpunt.

Vervolgens is de dataset opgeschoond en geherstructureerd. Opschonen omvat het weghalen van onjuiste karakters, zoals een ‘?’ die in de oorspronkelijke bron een ‘ij’ is, maar ook het wegfilteren van adelijke titels, zoals ‘Atjeh, koningin van’. Ook duplicaten binnen één cel zijn gecorrigeerd, zodat deze maar één ervan bevat.

Daarna herstructureerde ik de data. De name kolom is aangewezen als de kolom voor de unieke plaatsnamen die later omgedoopt zou worden tot unique_name. standard_name_rgp_gm hermoemde ik later tot standard_name. Daarom zijn spelvarianten in alternate_name_cd verplaatst naar de kolom met de unieke namen, zodat alle spelvarianten in één kolom staan. Ik maakte ook een variabele die elke plaats een ID gaf. Zo

Al deze activiteiten vereisten dat ik in Python programmeerde. Het was fijn hiermee te beginnen, want ik had al een tijdje niet meer geprogrammeerd in deze taal en de benodigde code was relatief makkelijk. Ondertussen begon ik ook met het schrijven van documentatie. Dit was de eerste keer dat ik documentatie schreef. Ik leerde hierbij wat ik moest vermelden in een documenatie. Eveneens vroeg het van mij om precies te zijn over de werkzaamheden, zodat anderen het werk kunnen evalueren en repliceren. Daarnaast bood het mij de kans om na te denken over wat ik deed.

2. Matching

Geschatte tijdsinvestering: 20%

De dataset is op zichzelfstaand echter minder interessant dan wanneer er ook links gemaakt worden met andere datasets. Op die manier kunnen onderzoekers data uit verschillende datasets gebruiken. Daarnaast kan de GLOBALISE dataset data lenen uit andere datasets. Daarom is ervoor gekozen om directe links te maken met de dataset Exploring Slave Trade in Asia (ESTA) door Pascal Konings (Konings 2023) en de Atlas of Mutual Heritage (AMH), wat een doorlopend project is door een aantal organisaties, waaronder het Nationaal Archief (Nationaal Archief, Rijksdienst het Cultureel Erfgoed, and Rijksmuseum Amsterdam and the Koninklijke Bibliotheek 2023). Deze datasets bevatten veel relevante historische geografische data, omdat ze grofweg dezelfde periodisering en geografische afbaking hebben als GLOBALISE.

Eerst maakte ik een match met ESTA. Hiertoe werd een exacte match gezocht tussen een unieke plaatsnaam in GLOBALISE’s dataset en die van ESTA, waarna het ID van ESTA toegevoegd werd aan de dataset. Bovendien voegde ik nieuwe rijen toe met spellingsvarianten uit ESTA die nog niet waren opgenomen in de plaatsnamendataset. Daarvoor creëerde ik de volgende functie:

import pandas as pd

# Append original names to a data frame
def append_original_places(main_dataframe, new_places_dataframe, places_col):
    # Make a list for original rows
    new_rows = []
    # Iterate over rows of data frame with new places
    for index, row in new_places_dataframe.iterrows():
        # Check if the name does not appear in main data frame
        if main_dataframe[places_col].eq(row[places_col]).any() == False: 
           # Append to the list with new rows
           new_rows.append(row)
        else:
            pass
    # Concatenate the main dataframe and the list with new names
    main_dataframe = pd.concat([main_dataframe, pd.DataFrame(new_rows, columns = main_dataframe.columns)], axis = 0)
    return main_dataframe

De functie vraagt twee datasets en de kolomsnaam waarin de plaatsen staan. Deze naam moet voor beide datasets gelijk zijn. In dit geval gaf ik de de plaatsnamendataset en de dataset met de plaatsen uit ESTA die ook in de GLOBALISE plaatsnamendataset zijn, inclusief het bijhorende GLOBALISE ID. Vervolgens zijn de rijen uit de tweede dataset toegoegd aan de eerste, indien de unieke naam uit ESTA nog niet opgenomen was in GLOBALISE’s dataset. De AMH onderging dezelfde handelingen, maar onderscheidt zich van ESTA in dat eerst de plekken in Amerika uit de dataset gefilterd moesten worden. De AMH bevat namelijk observaties voor zowel de VOC als de Westindische Compagnie. Dit was niet lastig, want de AMH heeft een variabele met daarin het huidige land waarin een plaats lag.

Aangezien de matches gebaseerd zijn op de plaatsnamen uit de datasets, zijn er fouten gemaakt. Het komt namelijk met regelmaat voor dat één plaats in de dataset van GLOBALISE meerdere ESTA of AMH ID’s heeft, omdat er meerdere plaatsen met dezelfde naam zijn. Een voorbeeld hiervan is Juda. Daarom schreef ik de functie id_confusion() om die gevallen te markeren:

import pandas as pd
import numpy as np

# Function that checks whether an ID in one data set contains multiple ID's in another
# Supply data frame, grouping ID, ID of which instances are to be count, 
# and a variable that indicates the existence of multiple ID's
def id_confusion(dataframe, reference_id, check_id, confusion_var_name):
    # Create a data frame with a row per reference ID and a list of unique ID's of another data frame
    id_set = pd.DataFrame(dataframe.groupby(reference_id)[check_id].apply(lambda x: set(x)).apply(lambda x: list(x))).reset_index()
    # Create a list for the counter and to indicate 
    # if there is 'ID confusion' (i.e. multiple ID's in one data set, but one in another)
    ID_count = []
    confusion_list = []
    for index, row in id_set.iterrows():
        # Filter out empty elements in the list
        row[check_id] = list(filter(None, row[check_id]))
        # Count elements and appent to list
        ID_count.append(len(row[check_id]))
    for count in ID_count:
        # If more than one ID accompanying the reference ID:
        if count > 1:
            # Append 1 = "yes"
            confusion_list.append(1)
        else:
            # Else 0 = "no"
            confusion_list.append(0)
    # Append the list to the data frame created above
    id_set[confusion_var_name] = confusion_list
    id_set = id_set[id_set[check_id].map(lambda id_list: len(id_list)) > 0]
    id_set[reference_id].replace('', np.nan, inplace = True)
    id_set.dropna(subset = [reference_id], inplace = True)
    return id_set

De gebruiker geeft de data set mee, waarbij hij of zij aangeeft welk ID als referentiekader dient en waartegen het andere ID vergeleken moet worden. Het resultaat is een dataframe met het GLOBALISE ID en het ID waartegen vergeleken wordt met daarbij de waarde 1 als er verwarring is over het ID en 0 indien dit niet het geval is. Djeddah heeft daarom de waarde 1, want het GLOBALISE ID heeft twee AMH ID’s:

unique_name standard_name glob_id source amh_id glob_amh_confusion
Juda Djeddah GLOB_156 GM/CD, AMH amh_918p 1
Djeddah Djeddah GLOB_156 GM/CD, AMH amh_752p 1
Ouidah Djeddah GLOB_156 AMH amh_918p 1
Fida Djeddah GLOB_156 AMH amh_918p 1
Whydah Djeddah GLOB_156 AMH amh_918p 1
Hueda Djeddah GLOB_156 AMH amh_918p 1
Whidah Djeddah GLOB_156 AMH amh_918p 1
Jiddah Djeddah GLOB_156 AMH amh_752p 1
Sjedda Djeddah GLOB_156 AMH amh_752p 1
Cidade de Iudda Djeddah GLOB_156 AMH amh_752p 1

Deze variabele diende enkel om handmatige correcties later te vergemakkelijken. Ze is niet opgenomen in de uiteindelijke dataset.

Tot slot verrijkte ik ook de dataset met de ID’s van de geografische databank GeoNames die geografische data bevat over hedendaagse plaatsen. Uiteindelijk zijn de GeoNames ID’s overgenomen die al door de AMH gebruikt waren, maar in de eerste instantie wilde ik zelf de API van GeoNames aanroepen. Daarvoor schreef ik de functie geonames_matcher:

import geocoder
import pandas as pd
import numpy as np

# Function to match names with GeoNames based on their name and a designated coordinate space
# Requires the geocoder module
# Input: dataframe containing placenames and coordinates; how many degrees in each direction has to be searched;
# variable names for place names, latitudes, longitudes, feature classes and codes; account name of GeoNames;
# Previous dataframe (GeoNames only processes a limited amount of input an hour, so the result has to be stored
# to process the remainder at another moment)
DEFAULT = object()
def geonames_matcher(coords_dataframe, degrees, place_var, lat_var, long_var, feature_classes, feature_codes, geo_acc, prev_df = DEFAULT):
    output_dataframe = pd.DataFrame({'name': pd.Series(dtype='str'),
                         'country': pd.Series(dtype='str'),
                         'geonames_id': pd.Series(dtype='int'),
                         'matched_on': pd.Series(dtype = "str")})
    # Concatenate previous data frame to the empty new one, if there is one
    # You need this, because of the limited number of requests you can give to GeoNames
    if prev_df is not DEFAULT:
        output_dataframe = pd.concat([output_dataframe, prev_df]).reset_index(drop = True)
    for index, row in coords_dataframe.iterrows():
        if (row[lat_var]) and (row[place_var] not in output_dataframe["matched_on"].unique()):
            # Extract the place name
            place = row[place_var]
            # Add and subtract three points to and from the coordinates to make a surface
            north = row[lat_var] + degrees
            south = row[lat_var] - degrees
            east = row[long_var] + degrees
            west = row[long_var] - degrees
            bbox = [west, south, east, north]
            # Search for the place in geo names, within the given surface
            geo = geocoder.geonames(place, featureCodes = feature_codes, \
            featureClasses = feature_classes, maxRows = 1, proximity = bbox, key = geo_acc)
            status = geo.status
            # Break if the limit is exceeded
            if "the hourly limit of 1000 credits for {account} has been exceeded".format(account = geo_acc) in status:
                break
            # Add as a row in the geonames dataframe
            new_row = pd.DataFrame({"name": geo.address,
                    "country": geo.country,
                    "geonames_id": geo.geonames_id,
                    "matched_on": place}, index=[0])
            output_dataframe = pd.concat([output_dataframe.loc[:], new_row]).reset_index(drop = True)
        elif not row[lat_var]: # Append place name without coordinates to the data frame
            place = row[place_var]
            new_row = pd.DataFrame({"name": np.nan,
                                    "country": np.nan,
                                    "geonames_id": np.nan,
                                    "matched_on": place}, index=[0])
            output_dataframe = pd.concat([output_dataframe.loc[:], new_row]).reset_index(drop = True)
    return output_dataframe

Deze functie is dus niet gebruikt, maar ik wil er hier kort aandacht aan besteden, omdat het de meest uitgebreide functie is die ik tot nu toe geschreven had. De functie neemt een dataset met de lengte- en breedtegraadvariabelen, waarna ze zoekt naar de plaats met dezelfde naam binnen de bounding box die de gebruiker maakt door een aantal degrees door te geven. Verder zijn er een paar andere variabelen die de gebruiker kan meegeven. Met betrekking tot de doorzoeking heeft GeoNames echter een limiet voor de hoeveelheid informatie die per uur met een gratis account opgevraagd kan worden. Daarom moest de data tussentijds opgeslagen (i.e. gepickeld) worden om daarna de overige rijen door GeoNames te halen ter matching. De voortgang tot dan toe kan aan de functie gegeven worden met het argument prev_df, wanneer de gebruiker de functie weer uitvoert. Het uiteindelijke resultaat is een dataframe met daarin de naam van de plaats met het bijbehorende land en GeoNames ID. Daarnaast is te zien wat de match was tussen de GLOBALISE dataset en GeoNames.

Tot slot voegde ik nog coördinatent toe, aangezien ESTA en de AMH die voor veel plekken hadden. Ondertussen werkte ik steeds de documentatie bij.

3. Handmatige opschoning en filtering

Geschatte tijdinvestering: 25%

Nadat de basis voor de dataset gelegd was, was het tijd om de dataset handmatig op te schonen. Dit kostte veel tijd, omdat er veel observaties waren die op de een of andere manier aangepast moesten worden. Correcties bestonden uit:

Alle wijzigingen zijn bijgehouden in een logboek. Daarnaast is de data van voor en na de correcties nog beschikbaar. Na de handmatige correcties heb ik voor enkele plaatsen handmatig coördinaten en GeoNames ID’s toegevoegd, maar er was niet voldoende tijd om dit voor veel plekken te doen. Alle werkprocessen zijn tevens omschreven in de documentatie en in een datasheet. De overlap tussen de documentatie en de datasheet is groot. Een datasheet is er voor bedoeld om een mogelijke gebruiker op de hoogte te stellen over de aard van een dataset en de bijbehorende sterke en zwakke punten. Het datasheet dat ik maakte volgt een vraag-en-antwoordstructuur.

De uiteindelijke dataset heeft de volgende vorm:

Zoals eerder gezegd zijn de variabelen country_geofeature en state_province_region zijn dus niet van mijn hand. De exacte betekenis per variabele is te vinden in de tabel onder 1. Semi-automatische opschoning.

Dit was de eerste versie van de dataset, maar hij kon meteen ingezet worden voor het maken van een ander stageproduct, namelijk de interactieve webapplicatie waarmee een gebruiker de plaatsnamen binnen de OBP kan verkennen. Daarvoor was echter eerst het corpus van de OBP nodig. Deze leverde Leon van Wissen aan. Vervolgens filterde ik de plaatsnamen uit de OBP, indien er een exacte match was tussen de plaats in de OBP en een plaatsnaam in de plaatsnamendataset. De plaatsnamendataset is echter slechte een selectie door een meervoud aan redenen. Lees de documentatie, het datasheet en stageverslag voor meer informatie hierover. Eén van de redenen is bijvoorbeeld dat ik werkte met een subset van de indexen van de GM.

De data die ik voor de applicatie gebruikte ziet er zo uit:

Met deze data kon het werk aan de app beginnen!

4. App-constructie

Geschatte tijdsinvestering: 35%

Anders dan bij de semi-automatische opschoning maakte ik hier gebruik van R. Waar Python een taal is die voor vele doeleinden gebruikt kan worden, is R ontworpen voor data-analyse en statistiekbeoefening. Binnen R is de mogelijkheid om interactieve webapplicaties te bouwen met de package Shiny. Nu is dit ook mogelijk in Python, maar ik koos voor R, omdat ik daar ervaring mee heb.

Aan de basis van de app liggen twee scripts en natuurlijk de data. Het ene script is voor de gebruikersinterface, het andere voor de server.

Het voert te ver om in detail in te gaan op de code, maar om een indicatie te geven is de volgende code gebruikt om de kaart te creëren. Aan de zijde van de gebruikersinterface:

tabPanel(title = "Mapping history",
             actionButton(
               "fullscreen", "Full screen version",
               onclick = "openFullscreen(document.getElementById('map_container'))"
             ),
             
             div(
               id = "map_container",
               leafletOutput(height = "950px", "map"),
               absolutePanel(
                 top = 90,
                 right = 20,
                 style = "color: #FFF",
                 h3("Parameters", style = "color:white"),
                 # Defining colors for the sliders
                 setSliderColor(c("#ff0055", "#33a9ac"), c(1, 2)),
                 # Slider for determining the period of the map
                 sliderInput("year_map", "Period", min(df$year), max(df$year),
                             value = range(df$year), step = 1, sep = ""),
                 
                 h4("Enter places to calculate the shortest distance"),
                 
                 textInput("input_place1", "First place", "Cadiz"),
                 
                 textInput("input_place2", "Second place", "Roti"),
                 
                 textOutput("distance"),
                 
                 h5(HTML("Attested HTR OBP places with amount of mentions. <br><br>
                    Distances are calculated using places names present <br>
                    in the specified period. <br><br>
                    Calculation method: the geodesic of the WGS84 ellipsoid"))
                    )
                 ),
             tags$scrip(HTML(js))
             )

De bovenstaande code creëert een knop waarmee de gebruiker de kaart op het volledige scherm kan weergeven. Vervolgens wordt een map_container aangemaakt die de kaart bevat (leafletOutput()) en de verschillende widgets die de gebruiker kan manipuleren, zoals de slider (year_map) voor de periode en twee balken voor tekstinvoer (input_place1 en input_place2). Achter de gebruikersinterface ligt de server:

   # Render the map
    output$map <- renderLeaflet({
      leaflet(data = df_map, options = list(worldCopyJump = TRUE)) %>%
        # Choosing a nice background: terrain
        addProviderTiles(providers$Stamen.TerrainBackground,
                           options = providerTileOptions(noWrap = FALSE,
                                                       minZoom = 3, 
                                                       maxZoom = 11)) %>%
        # Set the first view
        setView(lng = 128, lat = -4, zoom = 3)
    })
    
    updateSelectizeInput(session, "input_place1", choices = unique(df$standard_name))
    
    observe({
      # Plot the line if two places are filled in by the user, otherwise no line
      if (input$input_place1 %in% df_filtered_map()$standard_name & 
          input$input_place2 %in% df_filtered_map()$standard_name) {
        # Calculate the distance between the two given places
        distance <- distm(c(coords()[1, "longitude"], coords()[1, "latitude"]),
                          c(coords()[2, "longitude"], coords()[2, "latitude"]), 
                          fun = distHaversine)
       
        # Add circles for the map based on filter
        leafletProxy("map", data = df_filtered_map(), session = session) %>%
          clearShapes() %>%
          addCircles(lng = ~longitude, lat = ~latitude,
                     popup = ~paste0("place: ", standard_name, ", mentions: ", frequency,
                                     ", GLOBALISE ID: ", sub("GLOB_", "", glob_id)),
                     color = "#ff0055",
                     # Circle size also depends on zoom level
                     radius = ~frequency*3*(1/input$map_zoom)) %>%
          addGeodesicPolylines(data = coords(), lng = ~longitude, lat = ~latitude, 
                               group = ~standard_name, color = "#ff0055",
                               steps = 10000, opacity = 0.50, 
                               smoothFactor = 0.75, weight = 3.5,
                               popup = ~paste0("The shortest distance between ", input$input_place1, 
                                               " and ", input$input_place2,
                                               " is ", as.integer(distance/1000), " km"))}
      else {
        leafletProxy("map", data = df_filtered_map(), session = session) %>%
          clearShapes() %>%
          addCircles(lng = ~longitude, lat = ~latitude,
                     popup = ~paste0("place: ", standard_name, ", mentions: ", frequency,
                                     ", GLOBALISE ID: ", sub("GLOB_", "", glob_id)),
                     color = "#ff0055",
                     radius = ~frequency*3*(1/input$map_zoom))
      }      
    })
   
    # Output text for the distance between two places
    output$distance <- renderText({if (input$input_place1 %in% df_filtered_map()$standard_name & 
                                       input$input_place2 %in% df_filtered_map()$standard_name) {
      paste0("Click the line for the distance between ", input$input_place1 , 
             " and ", input$input_place2)
      } else {
        paste0("Incorrect input")
        }
    })

Het eerste gedeelte rendert de kaart. Daarna volgt het script dat de lijn tussen twee door de gebruiker gespecificeerde plekken plot en de afstand berekend. De lijn houdt rekening met het feit dat de wereld op een cilinder is geprojecteerd in het geval van de mercatorprojectie. Daarom is de lijn niet recht. Je kan de lijn alleen plotten als de plaatsen geobserveerd zijn binnen de door de gebruiker bepaalde periode met de slider (year_map). De coördinaten worden dus uit een gefilterde dataset gehaald (df_filtered_map()) Voor die berekening zijn coördinaten nodig die als volgt zijn opgevraagd:

   coords <- reactive({
     if (input$input_place1 %in% df_filtered_map()$standard_name & 
         input$input_place2 %in% df_filtered_map()$standard_name) {
       data.frame(filter(df_filtered_map(), standard_name %in% c(input$input_place1, input$input_place2)))
     }
   })

De app geeft een melding indien de plaats die de gebruiker opgeeft niet in df_filtered_map() zit. Eveneens bepalen de selectie van de periode en het zoomniveau de grootte van de cirkels. De cirkels weerspiegelen de hoeveelheid vermeldingen van een bepaalde plaats binnen de gegeven periode. Het resultaat is een interactieve kaart (Figuur 2)!

Figuur 2. Interactieve kaart Figuur 2. Interactieve kaart

De gehele applicatie met de andere visualiseringen een een korte introductie en documentatie zijn online te vinden en is te runnen vanuit de portfolio.

5. Presentatie

Geschatte tijdsinvestering: 5%

Tot slot had ik de gelegenheid om de bovenstaande producten te presenteren tijdens een datasprint. Deze activiteit heb ik niet opgenomen in Figuur 1, omdat het relatief weinig werk was, maar is desondanks noemenswaardig. De datasprint had als thema het verzamelen van data over plaatsen uit oude kaarten en het koppelen van verschillende plaatsnamendatasets aan elkaar via een historisch-geografische databank genaamd de World Historical Gazetteer. Hieronder is de presentatie te zien die ik gaf (vanaf Places all around a large part of the world in the VOC archives):

alt : datasprint_presentatie.pdf

De presentatie gaat in op de dataset en de applicatie, zich focussende op de sterke en zwakke kanten van de dataset en welke bijdrage de datasprint kan leveren om de creatie van plaatsnamendatasets te vergemakkelijken. Een voorbeeld is dat nu nog veel coördinaten ontbreken, maar dat het extraheren van coördinaten uit oude kaarten door ze op een moderne kaart te leggen een oplossing voor deze uitdaging zou kunnen bieden. Na de presentatie nam ik deel aan de sessie voor de omvorming van de data naar een format dat de World Historical Gazetteer accepteert.

Conclusie

Tijdens mijn stage heb ik de mogelijkheid gekregen om veel te leren over werken met data en de mogelijke valkuilen die zich voordoen daarbij. Het documenteren van mijn werk was daarom van groot nut, omdat het mij liet nadenken over wat ik deed en hoe dat de representativiteit van de data beïnvloedde. Denk daarbij bijvoorbeeld aan de steekproeftrekking. Vooral het datasheet hielp bij de reflectie, omdat deze al vaststaande vragen bevatte die ik vervolgens moest beantwoorden. De app liet mij weer op een heel andere wijze naar de data kijken, namelijk hoe ik deze het beste kan visualiseren. Aanvankelijk was het niet gepland dat er een presentatie zou komen, dus ik was blij dat die gelegenheid er was, zodat ik ook aan mijn presentatievaardigheden kan werken. Om deze redenen denk ik dat de dataset, documentatie, datasheet, applicatie en de presentatie de stageproducten zijn die een mooi beeld schetsen van mijn bezigheden.

Bibliografie

Coolhaas, Willem, Jurrien van Goor, J. E. Schooneveld-Oosterling, and Hugo s’Jacob. 1960. Generale Missiven van Gouverneurs-Generaal En Raden Aan Heren XVII Der Verenigde Oostindische Compagnie. Grote Serie.
Konings, Pascal. 2023. ESTA Database Locations.” IISH Data Collection. https://hdl.handle.net/10622/IWJMHH.
Nationaal Archief, Rijksdienst het Cultureel Erfgoed, and Rijksmuseum Amsterdam and the Koninklijke Bibliotheek. 2023. “Atlas of Mutual Heritage.” World Historical Gazetteer. http://whgazetteer.org/datasets/819.
Sholihah, Fanada, Marco Roling, and Jelle Gerbrandy. 2019. “Corpus Diplomaticum 1595-1799.” https://sejarah-nusantara.anri.go.id/corpusdiplomaticum/.